/*
 * @Author: xiaosihan
 * @Date: 2021-03-29 22:29:27
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2021-11-07 19:32:43
 */



import _ from "lodash";
import * as React from "react";
import { ErrorInfo } from "react";
import * as THREE from "three";
import { Camera, Scene, WebGLRenderer, Object3D as THREEObject3D } from "three";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { ReactThreeContext } from "../Renderer/Renderer";
import { THREEMouseEvent } from "../Renderer/THREEMouse";
import { Object3DEvent, ReactThreeContextType } from "../Renderer/type";
import Transition from "../xrt_util/Transition";
import xrt_util from "../xrt_util/xrt_util";
import GlowPass from "./GlowPass";
import Object3D from "./Object3D";
import { Vector3, Vector3Call } from "./type";


export interface Object3DComponentProps {
    /**
     * @ const { dev } = this.context;
     * @ 由Renderer 组件传递下来
     */
    dev?: boolean,//-------------------------------开发模式

    name?: string,//--------------------------------取个名字方便调试
    /**
     * @贝塞尔曲线 [0.8, 0, 0.2, 1]
     */
    bezier?: [number, number, number, number],
    /**
     * 过渡动画时间 500 单位毫秒
     */
    duration?: number,//---------------------------过渡时间
    /**
     *@dom标签 传入一个组件
     */
    title?: JSX.Element;//---------------------dom标签
    /**
     *@三维坐标 {x?:0,y?:0,z?:0}
     *@三维坐标函数 (renderer: THREE.WebGLRenderer,scene:THREE.Scene,camera: THREE.Camera,geometry: THREE.BufferGeometry,material: THREE.Material,object3d: Object3D) => { return {x?:0,y?:0,z?:0} };
     */
    position?: Vector3 | Vector3Call,//------------三维坐标
    rotation?: Vector3 | Vector3Call,//------------三维旋转
    scale?: Vector3 | Vector3Call,//---------------三维缩放
    lookAt?: Vector3 | Vector3Call | "camera",//--------------全局朝向

    // 其他
    className?: string,//--------------------------对象分类  射线碰撞时可以设置那些分类可以参数相交计算
    castShadow?: boolean,//------------------------创建阴影
    receiveShadow?: boolean,//---------------------创建阴影
    frustumCulled?: boolean,//---------------------是否判断如果模型没有出现在相机的视野中就不执行渲染方法
    renderOrder?: number,//------------------------渲染顺序
    visible?: boolean,//---------------------------是否可见
    userData?: { [key: string]: any }, //----------用户自定义输数据
    clipping?: boolean,//--------------------------允许父元素传递下来裁剪面
    clipIntersection?: boolean,//-------------------裁剪方式

    onBeforeRender?: ( //-------------------渲染前执行的回调
        renderer: WebGLRenderer,
        scene: Scene,
        camera: Camera,
        object3d: Object3D,
    ) => void,

    onAfterRender?: (//---------------------渲染后执行的回调
        renderer: WebGLRenderer,
        scene: Scene,
        camera: Camera,
        object3d: Object3D,
    ) => void,

    // 鼠标相关
    cursor?: string;//-----------------------------鼠标样式
    onMousemove?: (e: THREEMouseEvent) => void,//--鼠标滑动事件
    onMouseleave?: (e: THREEMouseEvent) => void,//-鼠标离开事件
    onMouseenter?: (e: THREEMouseEvent) => void,//-鼠标进入事件
    onMousedown?: (e: THREEMouseEvent) => void,//--鼠标按下事件
    onMouseup?: (e: THREEMouseEvent) => void,//----鼠标弹起事件
    onClick?: (e: THREEMouseEvent) => void,//------鼠标点击事件

    //辉光效果相关
    glow_enable?: boolean,//-----------------------启用辉光
    glow_visibleColor?: string, //-----------------可见区域辉光颜色
    glow_hiddenColor?: string, //------------------不可见区域辉光颜色
    glow_depthTest?: boolean, //--------------------深度测试 = false 时 不进行遮挡计算
    glow_opacity?: number, //-----------------------辉光透明度
    glow_fill?: boolean, //-------------------------辉光填充整个模型区域
    glow_blurWidth?: number, //---------------------辉光厚度
    glow_pulsePeriod?: number, //-------------------辉光闪烁间隔时间_单位秒

}

export interface State {
}

export default class Object3DComponent<P = {}, S = {}> extends React.Component<P & Object3DComponentProps, S & State, any> {
    constructor(props: P & Object3DComponentProps) {
        super(props);

        //混入mixin_componentDidMount生命周期
        let originComponentDidMount = this.componentDidMount;
        this.componentDidMount = function () {
            this.mixin_componentDidMount.apply(this);
            if (originComponentDidMount) {
                originComponentDidMount.apply(this);
            }
        }

        //混入mixin_componentDidUpdate生命周期
        let originComponentDidUpdate = this.componentDidUpdate;
        this.componentDidUpdate = function (nextProps: Readonly<P & Object3DComponentProps>, nextState: Readonly<S & State>, snapshot: any) {
            this.mixin_componentDidUpdate.apply(this, [nextProps, nextState, snapshot]);
            if (originComponentDidUpdate) {
                originComponentDidUpdate.apply(this, [nextProps, nextState, snapshot]);
            }
        }

        //混入mixin_componentDidCatch生命周期
        let originComponentDidCatch = this.componentDidCatch;
        this.componentDidCatch = function (error: Error, errorInfo: ErrorInfo) {
            this.mixin_componentDidCatch.apply(this, [error, errorInfo]);
            if (originComponentDidCatch) {
                originComponentDidCatch.apply(this, [error, errorInfo]);
            }
        }

        //混入mixin_componentWillUnmount生命周期
        let originComponentWillUnmount = this.componentWillUnmount;
        this.componentWillUnmount = function () {
            this.mixin_componentWillUnmount.apply(this);
            if (originComponentWillUnmount) {
                originComponentWillUnmount.apply(this);
            }
        }

        // render 生命周期需要单独处理
        let originRender = this.render || (() => this.props.children);

        this.render = () => {
            return (
                <ReactThreeContext.Provider
                    value={{
                        ...this.context,
                        parent: this.object3d,
                        dev: _.isUndefined(this.props.dev) ? this.context.dev : this.props.dev
                    }}
                >
                    {originRender.apply(this)}
                </ReactThreeContext.Provider>
            );
        }

        // 创建基础对象object3d;
        this.object3d = new (Function("THREEObject3D", `
            return class ${this.getConstructorName()} extends THREEObject3D {};
        `)(THREEObject3D));

    }

    static contextType = ReactThreeContext;

    declare context: ReactThreeContextType;

    // 组件是否安装完成
    isMount = false;

    //@ts-ignore
    uuid = THREE.Math.generateUUID();

    object3d!: Object3D;

    getConstructorName() {

        if (this.props.name !== undefined) {
            return this.props.name;
        }

        return _.get(this, ["__proto__", "constructor", "name"]);
    }


    getSnapshotBeforeUpdate(prevProps: Readonly<P & Object3DComponentProps>, prevState: Readonly<S & State>) {
        return "xsh-react-three";
    }

    mixin_componentDidMount() {
        this.isMount = true;

        if (!this.object3d.hasOwnProperty("clipPlanes")) {
            this.object3d.clipPlanes = [];
        }

        const { parent } = this.context;

        parent.add(this.object3d);

        // 设置过渡变换参数
        this.setTansformParam();

        //设置 object3d 上的变换参数
        this.setObjectParam();

        //设置其他参数
        this.setOtherParam();

        this.object3d.updateWorldMatrix(true, false);
        this.object3d.updateMatrixWorld(true);

        // 设置鼠标相关的事件
        this.setMouseEvent();

        //设置辉光相关的参数
        this.setGlow();

        // 设置裁剪面
        this.setClipPlanes();

        // 添加对象上面的监听事件
        this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.onBeforeRender);
        // this.object3d.addEventListener(Object3DEvent.AFTER_RENDER, this.onAfterRender);

        // 执行生命周
        if (this.componentBeforeRender) {
            this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, ({ type, target, data: { renderer, scene, camera } }) => {
                if (this.componentBeforeRender) {
                    this.componentBeforeRender(renderer as THREE.WebGLRenderer, scene as THREE.Scene, camera as THREE.Camera, target as Object3D);
                }
            });
        }

    }

    // 判断组件是否重绘的默认规则
    mixin_componentDidUpdate(nextProps: Readonly<P & Object3DComponentProps>, nextState: Readonly<S & State>, snapshot: any): void {

        // 设置位置参数
        this.setTansformParam();

        //设置其他参数
        this.setOtherParam();

        // 设置鼠标相关的事件
        this.setMouseEvent();

        //设置辉光相关的参数
        this.setGlow();

        // 设置裁剪面
        this.setClipPlanes();

        // 添加对象上面的监听事件 先移除再添加避免重复添加
        this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.onBeforeRender);
        this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.onBeforeRender);

        // this.object3d.removeEventListener(Object3DEvent.AFTER_RENDER, this.onAfterRender);
        // this.object3d.addEventListener(Object3DEvent.AFTER_RENDER, this.onAfterRender);
    }

    // 判断组件是否重绘的默认规则
    shouldComponentUpdate(nextProps: Readonly<P & Object3DComponentProps>, nextState: Readonly<State>, nextContext: ReactThreeContextType): boolean {
        const propsIsChange = !_.isEqual(this.props, nextProps);
        const stateIsChange = !_.isEqual(this.state, nextState);
        const contextIsChange = !_.isEqual(this.context, nextContext);
        return propsIsChange || stateIsChange || contextIsChange;
    }

    // 组件异常捕获
    mixin_componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    }

    // 当组件卸载时对组件进行清理
    mixin_componentWillUnmount() {
        // 避免在组件卸载后异步调用此方法而报错
        this.setState = () => { };
        this.isMount = false;

        // 从父元素中移除
        const { parent, composer } = this.context;

        parent.remove(this.object3d);

        // 从鼠标事件数组中移除
        const { mouseEventMesh } = this.context;
        if (mouseEventMesh.includes(this.object3d)) {
            let index = mouseEventMesh.indexOf(this.object3d);
            mouseEventMesh.splice(index, 1);
        }

        if (this.glowPass) {
            (composer as EffectComposer).removePass(this.glowPass);
        }

        this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.onBeforeRender);
        // this.object3d.removeEventListener(Object3DEvent.AFTER_RENDER, this.onAfterRender);
    }

    // 新增的生命周期 
    componentBeforeRender?(
        renderer: WebGLRenderer,
        scene: Scene,
        camera: Camera,
        object3D: Object3D
    ): void;

    // 新增的生命周期 
    componentAfterRender?(
        renderer: WebGLRenderer,
        scene: Scene,
        camera: Camera,
        object3D: Object3D
    ): void;

    // 自己的世界坐标
    worldPosition = new THREE.Vector3(0, 0, 1);

    // 朝向坐标
    lookAtPosition = new THREE.Vector3(0, 0, 1);

    // 位置变换动画
    Tposition = new Transition();

    //缩放变换参数
    Tscale = new Transition();

    // 旋转变换动画
    Trotation = new Transition();

    // 创建有过渡效果的lookAt对象
    TlookAt = new Transition();

    // 辉光渲染通道
    glowPass?: GlowPass;

    // 整个场景渲染之前执行
    onBeforeRender = ({ type, target, data: { renderer, scene, camera } }: any) => {
        let keepRun = this.setObjectParam();

        if (this.props.onBeforeRender) {
            this.props.onBeforeRender(renderer, scene, camera, target);
            keepRun = true;
        }

        // 结束这个事件监听
        if (!keepRun || !this.object3d.visible) {
            this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.onBeforeRender);
        }
    }

    // 整个场景渲染之后执行
    // onAfterRender = ({ type, target, data: { renderer, scene, camera } }: any) => {
    //     if (this.object3d.visible && this.props.onAfterRender) {
    //         this.props.onAfterRender(renderer, scene, camera, target);
    //     } else {
    //         // 结束这个事件监听
    //         this.object3d.removeEventListener(Object3DEvent.AFTER_RENDER, this.onAfterRender);
    //     }
    // }

    // 设置过渡变换参数
    setTansformParam() {

        const { bezier, duration = 500, position, rotation, scale, lookAt } = this.props;
        const { renderer, scene, camera } = this.context;

        // 位置
        if (_.isObject(position) || _.isUndefined(position)) {
            const { x, y, z } = xrt_util.parseVector3(position, renderer, scene, camera, this.object3d);

            this.Tposition
                .setBezier(bezier)
                .setDuration(duration)
                .reset({ ...this.object3d.position })
                .set({ x, y, z });
        }

        // 缩放
        if (_.isObject(scale) || _.isUndefined(scale)) {
            const { x, y, z } = xrt_util.parseScale(scale, renderer, scene, camera, this.object3d);
            this.Tscale
                .setBezier(bezier)
                .setDuration(duration)
                .reset({ ...this.object3d.scale })
                .set({ x, y, z });
        }

        // 朝向计算 优先于旋转计算
        if (_.isObject(lookAt)) {

            const { x, y, z } = xrt_util.parseVector3(lookAt);

            // 朝向
            this.TlookAt
                .setBezier(bezier)
                .setDuration(duration)
                .reset({ ...this.lookAtPosition })
                .set({ x, y, z });

            // 旋转
        } else if (_.isObject(rotation) || _.isUndefined(rotation)) {

            const { x, y, z } = xrt_util.parseEuler(rotation);

            // 旋转
            this.Trotation
                .setBezier(bezier)
                .setDuration(duration)
                .reset({ ...this.object3d.rotation })
                .set({ x, y, z });
        }
    }

    // 设置object3d 对象上的变换参数
    setObjectParam() {
        const { renderer, scene, camera } = this.context;
        const { geometry, material } = this.object3d;
        const { position, scale, lookAt, rotation } = this.props;

        // 需要更新矩阵
        let keepRun = false;

        let TpositionisDone = this.Tposition.isDone();

        // 修改位置
        if (position instanceof Function) {

            const P = xrt_util.parseVector3(position, renderer, scene, camera, this.object3d);
            this.object3d.position.copy(P);
            keepRun = true;

        } else if (!TpositionisDone) {
            const { x, y, z } = this.Tposition.getJson();
            this.object3d.position.set(x, y, z);
            keepRun = true;

        }

        if ((position instanceof Function || !TpositionisDone) && (_.isObject(lookAt) || lookAt instanceof Function)) {
            this.object3d.lookAt(this.lookAtPosition);
        }

        // 修改缩放
        if (scale instanceof Function) {

            const S = xrt_util.parseScale(scale, renderer, scene, camera, this.object3d);
            this.object3d.scale.copy(S);
            keepRun = true;

        } else if (!this.Tscale.isDone()) {
            const { x, y, z } = this.Tscale.getJson();
            this.object3d.scale.set(x, y, z);
            keepRun = true;
        }

        // 朝向计算优先于旋转
        if (lookAt instanceof Function) {

            const L = xrt_util.parseVector3(lookAt as Vector3Call, renderer, scene, camera, this.object3d);
            this.object3d.lookAt(L);
            this.lookAtPosition.copy(L);
            keepRun = true;

        } else if (lookAt === "camera") {

            this.lookAtPosition.set(0, 0, 1);
            this.lookAtPosition.transformDirection(camera.matrixWorld);
            this.object3d.getWorldPosition(this.worldPosition);
            this.lookAtPosition.add(this.worldPosition);
            this.object3d.lookAt(this.lookAtPosition);
            keepRun = true;
        } else if (_.isObject(lookAt) && !this.TlookAt.isDone()) {
            const { x, y, z } = this.TlookAt.getJson();
            this.lookAtPosition.set(x, y, z);
            this.object3d.lookAt(this.lookAtPosition);
            keepRun = true;

        } else if (rotation instanceof Function) {

            const R = xrt_util.parseEuler(rotation, renderer, scene, camera, this.object3d);
            this.object3d.rotation.copy(R);
            keepRun = true;

        } else if (rotation instanceof Object && !this.Trotation.isDone()) {
            const { x, y, z } = this.Trotation.getJson();
            this.object3d.rotation.set(x, y, z);
            keepRun = true;
        }

        if (keepRun) {
            this.object3d.updateMatrix();
        }

        return keepRun;
    }

    setOtherParam() {
        const {
            className,
            castShadow = false,
            receiveShadow = false,
            frustumCulled = false,
            renderOrder = 0,
            visible = true,
            userData
        } = this.props;

        Object.assign(this.object3d, {
            className,
            castShadow,
            receiveShadow,
            frustumCulled,
            renderOrder,
            visible,
            userData
        });
    }

    // 设置鼠标相关的事件
    setMouseEvent() {
        let {
            cursor,
            onMousemove,
            onMouseleave,
            onMouseenter,
            onMousedown,
            onMouseup,
            onClick,
            title
        } = this.props;

        const { TitleDomController } = this.context;

        if (title) {
            let originOnMouseenter = onMouseenter;
            onMouseenter = ((e: THREEMouseEvent) => {
                TitleDomController.setTitleDomInstanceComponent(title);
                originOnMouseenter && originOnMouseenter(e);
            }) as any;

            let originOnMousemove = onMousemove;
            onMousemove = ((e: THREEMouseEvent) => {
                const { offsetX, offsetY } = e;
                TitleDomController.setTitleDomInstancePosition(offsetX, offsetY);
                originOnMousemove && originOnMousemove(e);
            }) as any

            let originOnMouseleave = onMouseleave;
            onMouseleave = ((e: THREEMouseEvent) => {
                TitleDomController.setTitleDomInstanceComponent(undefined);
                originOnMouseleave && originOnMouseleave(e);
            }) as any;
        }

        Object.assign(this.object3d, { cursor, onMousemove, onMouseleave, onMouseenter, onMousedown, onMouseup, onClick });

        const { mouseEventMesh } = this.context;

        // 加入到鼠标拾取数组中
        if ((cursor || onMousemove || onMouseleave || onMouseenter || onMousedown || onMouseup || onClick) && !mouseEventMesh.includes(this.object3d)) {

            mouseEventMesh.push(this.object3d);

        } else if (!(cursor || onMousemove || onMouseleave || onMouseenter || onMousedown || onMouseup || onClick) && mouseEventMesh.includes(this.object3d)) {

            let index = mouseEventMesh.indexOf(this.object3d);
            mouseEventMesh.splice(index, 1);
        }
    }

    //设置辉光相关的参数
    setGlow() {
        const { scene, camera, composer } = this.context;

        const { glow_enable = false } = this.props;

        if (glow_enable) {

            this.glowPass = this.glowPass || new GlowPass(scene, camera, this.object3d);

            this.glowPass!.object3d = this.object3d;

            (composer as EffectComposer).addPass(this.glowPass!);

            const {
                glow_visibleColor, //------可见区域辉光颜色
                glow_hiddenColor, //-------不可见区域辉光颜色
                glow_depthTest = false, //--------深度测试 = false 时 不进行遮挡计算
                glow_opacity = 1, //---------------辉光透明度
                glow_fill = false, //--------------辉光填充整个模型区域
                glow_blurWidth = 10, //----------------辉光厚度
                glow_pulsePeriod = 0, //---------------辉光闪烁间隔时间_单位秒
            } = this.props;

            Object.assign(this.glowPass!, {
                glow_visibleColor,
                glow_hiddenColor,
                glow_depthTest,
                glow_opacity,
                glow_fill,
                glow_blurWidth,
                glow_pulsePeriod,
            });

        } else if (this.glowPass) {
            composer.removePass(this.glowPass);
        }

    }

    // 设置裁剪面
    setClipPlanes() {
        const { clipping = true, clipIntersection = false } = this.props;
        const { parent } = this.context;

        if (parent.clipPlanes) {
            parent.clipPlanes.map(p => {
                if (clipping) {
                    this.object3d.addClipPlane(p);
                } else {
                    this.object3d.removeClipPlane(p);
                }
            });
        }

        if (this.object3d.material) {
            Object.assign(this.object3d.material, {
                clipIntersection,
                clipShadows: clipping,
                clippingPlanes: clipping ? this.object3d.clipPlanes : null
            });
        }

    }

}